/*write.cpp
 *This program writes specified file to specified block on created RAID.
 *Created on: 2013.12.24
 *Author: Petrunin D.S.
 */
#include <iostream>
#include <stdio.h>
#include <cstdlib>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
//---------------------------
#include "base.h"

char* genParity(char** forParity, int nDisks, int fileS) {
	int fBlockS = fileS / (nDisks - 1) + fileS % (nDisks - 1);
	char* res = new char[fBlockS];
	char XORres;
	for(int i = 0; i < fBlockS; i++) {
		XORres = 0;
		for(int j = 1; j < nDisks; j++)
			XORres = XORres ^ forParity[j][i];
		res[i] = XORres;
	}
	return res;
}

int writeRAID0(FILE* file, int fileS, int blockN, int nDisks,	int nBlocks, int blockS) {
	if (mapping(nDisks, nBlocks, blockS, false) == 1)
		return 1;
	int fBlockS = fileS / nDisks;
	int offset = (blockN - 1) * blockS;
	for (int i = 1; i <= nDisks; i++) {
		if (i == nDisks)
			fBlockS += fileS % nDisks;
		fread(mappedDataArray[i] + offset, 1, fBlockS, file);
		if (fBlockS < blockS)
			memset(mappedDataArray[i] + offset + fBlockS, 0, blockS - fBlockS);
	}
	fclose(file);
	if (unmapping(nDisks, nBlocks, blockS, false) == 1)
		return 1;
	return 0;
}

int writeRAID1(FILE* file, int fileS, int blockN, int nDisks,	int nBlocks, int blockS) {
	if (mapping(nDisks, nBlocks, blockS, false) == 1)
		return 1;
	int offset = (blockN - 1) * blockS;
	for (int i = 1; i <= nDisks; i++) {
		rewind(file);
		fread(mappedDataArray[i] + offset, 1, fileS, file);
		if (fileS < blockS)
			memset(mappedDataArray[i] + offset + fileS, 0, blockS - fileS);
	}
	fclose(file);
	if (unmapping(nDisks, nBlocks, blockS, false) == 1)
		return 1;
	return 0;
}

int writeRAID3(FILE* file, int fileS, int blockN, int nDisks,	int nBlocks, int blockS) {
	if (mapping(nDisks, nBlocks, blockS, false) == 1)
		return 1;
	int fBlockS = fileS / (nDisks - 1) + 1;
	int offset = (blockN - 1) * blockS;
	char XORres;
	int size;
	for (int k = 0; k < fBlockS; k++) {
		XORres = 0;
		for (int i = 1; i < nDisks; i++) {
			size = fread(mappedDataArray[i] + offset + k, 1, 1, file);
			if (size == 0)
				*(mappedDataArray[i] + offset + k) = 0;
			XORres = XORres ^ *(mappedDataArray[i] + offset + k);
		}
		*(mappedDataArray[nDisks] + offset + k) = XORres;
	}
	if (fBlockS < blockS) {
		for (int i = 1; i <= nDisks; i++)
			memset(mappedDataArray[i] + offset + fBlockS, 0,	blockS - fBlockS);
	}
	fclose(file);
	if (unmapping(nDisks, nBlocks, blockS, false) == 1)
		return 1;
	return 0;
}

int writeRAID4(FILE* file, int fileS, int blockN, int nDisks,	int nBlocks, int blockS) {
	if (mapping(nDisks, nBlocks, blockS, false) == 1)
		return 1;
	int fBlockS = fileS / (nDisks - 1);
	int offset = (blockN - 1) * blockS;
	char** forParity = new char*[nDisks];
	for (int i = 1; i < nDisks; i++) {
		if (i == nDisks - 1)
			fBlockS += fileS % (nDisks - 1);
		fread(mappedDataArray[i] + offset, 1, fBlockS, file);
		if (fBlockS < blockS)
			memset(mappedDataArray[i] + offset + fBlockS, 0, blockS - fBlockS);
		forParity[i] = mappedDataArray[i] + offset;
	}
	char* data = genParity(forParity, nDisks, fileS); //Write parity to mappedData with offset
	memcpy(mappedDataArray[nDisks] + offset, data, fBlockS);
	if (fBlockS < blockS)
		memset(mappedDataArray[nDisks] + offset + fBlockS, 0, blockS - fBlockS);
	fclose(file);
	if (unmapping(nDisks, nBlocks, blockS, false) == 1)
		return 1;
	return 0;
}

int writeRAID5(FILE* file, int fileS, int blockN, int nDisks,	int nBlocks, int blockS) {
	if (mapping(nDisks, nBlocks, blockS, false) == 1)
		return 1;
	int fBlockS = fileS / (nDisks - 1); //parts of file for writing to blocks;
	int offset = (blockN - 1) * blockS;
	char** forParity = new char*[nDisks];
	int parityDisk = nDisks - (blockN - 1); //number of disk, which contains parity block
	int k = 0;
	for (int i = 1; i <= nDisks; i++) {
		if (parityDisk != i) {
			k++;
			if (k == nDisks - 1)
				fBlockS += fileS % (nDisks - 1);
			fread(mappedDataArray[i] + offset, 1, fBlockS, file);
			if (fBlockS < blockS)
				memset(mappedDataArray[i] + offset + fBlockS, 0, blockS - fBlockS);
			forParity[k] = mappedDataArray[i] + offset;
		}
	}
	//Write parity to mappedData with offset
	char* data = genParity(forParity, nDisks, fileS);
	memcpy(mappedDataArray[parityDisk] + offset, data, fBlockS);
	if (fBlockS < blockS)
		memset(mappedDataArray[parityDisk] + offset + fBlockS, 0, blockS - fBlockS);
	fclose(file);
	if (unmapping(nDisks, nBlocks, blockS, false) == 1) return 1;
	return 0;
}

int main(int argc, char* argv[]) {

	if (argc < 3) {
		cout << "Not enough parameters\n";
		return 1;
	}
	FILE* config = fopen("config", "r");
	if (config == NULL) {
		cout << "Can't open config file.\n";
		return 1;
	}
	int nDisks, nBlocks, blockS, RAIDLevel;
	fscanf(config, "%i\n%i\n%i\n%i\n", &nDisks, &nBlocks, &blockS,
			&RAIDLevel);
	fclose(config);

	if (failedDisk(nDisks, false)) {
		cout << "Rebuild RAID.\n";
		return 1;
	}
	int blockN = atoi(argv[1]);
	if ((blockN == 0) || (blockN > nBlocks)) {
		cout << "Number of blocks must be 1.." << nBlocks << "\n";
		return 1;
	}

	char* fileName = argv[2];
	struct stat file_stat;
	int statf = stat(fileName, &file_stat);
	if (statf != 0) {
		cout << "Can't get statistics of file " << fileName << ".\n";
		return 1;
	}
	if (file_stat.st_size > blockS) {
		cout << "Size of file must be <= " << blockS << "\n";
		return 1;
	}

	FILE * file;
	file = fopen(fileName, "rb+");
	if (file == NULL) {
		cout << "Can't open file " << file << "\n";
		return 1;
	}

	switch (RAIDLevel) {
	case 0:
		return writeRAID0(file, file_stat.st_size, blockN, nDisks,
				nBlocks, blockS);
	case 1:
		return writeRAID1(file, file_stat.st_size, blockN, nDisks,
				nBlocks, blockS);
	case 3:
		return writeRAID3(file, file_stat.st_size, blockN, nDisks,
				nBlocks, blockS);
	case 4:
		return writeRAID4(file, file_stat.st_size, blockN, nDisks,
				nBlocks, blockS);
	case 5:
		return writeRAID5(file, file_stat.st_size, blockN, nDisks,
				nBlocks, blockS);
	}
}

